<?php
// $Id: Theme.php 27 2010-11-16 12:49:54Z mjohnson60@gmail.com $

/**
 * Enter description here ...
 * 
 * @author Mark Phillip Johnson
 * @version 1.0
 * @copyright Copyright (c) 2010, markjohnson
 * @license http://www.gnu.org/licenses/gpl.html The GNU General Public License v3.0
 *
 */
class Theme extends Object
{
	/**
	 * @var string Enter description here ...
	 */
	protected $name = '';
	
	/**
	 * @var string Enter description here ...
	 */
	protected $displayName = '';
	
	/**
	 * @var int Enter description here ...
	 */
	protected $version = 1;
	
	/**
	 * @var unknown_type Enter description here ...
	 */
	protected static $description = '';
	
	/**
	 * @var array Enter description here ...
	 */
	private static $list = array();
	
	/**
	 * Enter description here ...
	 * 
	 */
	public function __construct()
	{
		parent::__construct();
	}
	
	/**
	 * Initialize the theme system given already loaded information. This
	 * function is useful to initialize a theme when no database is present.
	 *
	 * @param $theme
	 *   An object with the following information:
	 *     filename
	 *       The .info file for this theme. The 'path' to
	 *       the theme will be in this file's directory. (Required)
	 *     owner
	 *       The path to the .theme file or the .engine file to load for
	 *       the theme. (Required)
	 *     stylesheet
	 *       The primary stylesheet for the theme. (Optional)
	 *     engine
	 *       The name of theme engine to use. (Optional)
	 * @param $base_theme
	 *    An optional array of objects that represent the 'base theme' if the
	 *    theme is meant to be derivative of another theme. It requires
	 *    the same information as the $theme object. It should be in
	 *    'oldest first' order, meaning the top level of the chain will
	 *    be first.
	 * @param $registry_callback
	 *   The callback to invoke to set the theme registry.
	 */
	public static function _init($theme, $base_theme = array(), $registry_callback = array('Renderer', 'loadRegistry')) 
	{
		global $theme_info, $base_theme_info, $theme_engine, $theme_path;
		
		$theme_info = $theme;
		$base_theme_info = $base_theme;
		
		$theme_path = dirname($theme->filename);
		
		// Prepare stylesheets from this theme as well as all ancestor themes.
		// We work it this way so that we can have child themes override parent
		// theme stylesheets easily.
		$final_stylesheets = array();
		
		// Grab stylesheets from base theme
		foreach ($base_theme as $base)
		{
			if (!empty($base->stylesheets))
			{
				foreach ($base->stylesheets as $media => $stylesheets)
				{
					foreach ($stylesheets as $name => $stylesheet)
					{
						$final_stylesheets[$media][$name] = $stylesheet;
					}
				}
			}
		}
		
		// Add stylesheets used by this theme.
		if (!empty($theme->stylesheets))
		{
			foreach ($theme->stylesheets as $media => $stylesheets)
			{
				foreach ($stylesheets as $name => $stylesheet)
				{
					$final_stylesheets[$media][$name] = $stylesheet;
				}
			}
		}
		
		// And now add the stylesheets properly
		foreach ($final_stylesheets as $media => $stylesheets)
		{
			foreach ($stylesheets as $stylesheet)
			{
				Athena::addCss($stylesheet, 'theme', $media);
			}
		}
		
		// Do basically the same as the above for scripts
		$final_scripts = array();
		
		// Grab scripts from base theme
		foreach ($base_theme as $base)
		{
			if (!empty($base->scripts))
			{
				foreach ($base->scripts as $name => $script)
				{
					$final_scripts[$name] = $script;
				}
			}
		}
		
		// Add scripts used by this theme.
		if (!empty($theme->scripts))
		{
			foreach ($theme->scripts as $name => $script)
			{
				$final_scripts[$name] = $script;
			}
		}
		
		// Add scripts used by this theme.
		foreach ($final_scripts as $script)
		{
			Athena::addScripts($script, 'theme');
		}
		
		$theme_engine = NULL;
		
		// Initialize the theme.
		if (isset($theme->engine))
		{
			// Include the engine.
			include_once './' . $theme->owner;
			
			$theme_engine = $theme->engine;
			if (function_exists($theme_engine . '_init'))
			{
				foreach ($base_theme as $base)
				{
					call_user_func($theme_engine . '_init', $base);
				}
				
				call_user_func($theme_engine . '_init', $theme);
			}
		}
		else
		{
			// include non-engine theme files
			foreach ($base_theme as $base)
			{
				// Include the theme file or the engine.
				if (!empty($base->owner))
				{
					include_once './' . $base->owner;
				}
			}
			// and our theme gets one too.
			if (!empty($theme->owner))
			{
				include_once './' . $theme->owner;
			}
		}
		
		call_user_func_array($registry_callback, array($theme, $base_theme, $theme_engine));
	}
	
	/**
	 * listThemes()
	 * 
	 * Enter description here ...
	 * 
	 * @param boolean $refresh
	 * @return Ambigous <multitype:, unknown>
	 */
	public static function listThemes($refresh = FALSE) 
	{
		$list = self::$list;
		
		if ($refresh) 
		{
			$list = array();
		}
		
		if (empty($list)) 
		{
			$themes = array();
			
			if (Db::isActive() && !defined('CONFIGURATION_MODE')) 
			{
				$result = Db::query("SELECT * FROM system WHERE type = '%s'", 'theme');
				
				while (($theme = Db::fetchObject($result)) !== FALSE) 
				{
					if (file_exists($theme->filename))
					{
						$theme->info = unserialize($theme->info);
						$themes[] = $theme;
					}
				}
			}
			else
			{
				$themes = System::_themeData();
			}
			
			foreach ($themes as $theme)
			{
				foreach ($theme->info['stylesheets'] as $media => $stylesheets)
				{
					foreach ($stylesheets as $stylesheet => $path)
					{
						$theme->stylesheets[$media][$stylesheet] = $path;
					}
				}
				
				foreach ($theme->info['scripts'] as $script => $path)
				{
					if (file_exists($path))
					{
						$theme->scripts[$script] = $path;
					}
				}
				
				if (isset($theme->info['engine']))
				{
					$theme->engine = $theme->info['engine'];
				}
				
				if (isset($theme->info['base_theme']))
				{
					$theme->base_theme = $theme->info['base_theme'];
				}
				
				// Status is normally retrieved from the database. Add zero values when
				// read from the installation directory to prevent notices.
				if (!isset($theme->status))
				{
					$theme->status = 0;
				}
				
				$list[$theme->name] = $theme;
			}
			
			self::$list = $list;
		}
		
		return $list;
	}
	
	/**
	 * invoke()
	 * 
	 * Enter description here ...
	 * 
	 * @return mixed
	 */
	public static function invoke() 
	{
		$args = func_get_args();
		$hook = array_shift($args);
		
		return call_user_func_array(array($this, $hook), $args);
	}
	
	/**
	 * Enter description here ...
	 * 
	 * @param array $existing
	 * @param string $type
	 * @param string $theme
	 * @param string $path
	 * @return array:
	 */
	public function theme(&$existing, $type, $theme, $path) 
	{
		return array();
	}
	
	/**
	 * attributes()
	 * 
	 * This is a function that will take in a key => value array 
	 * and produce a string of HTML attributes.
	 * 
	 * ie. an array like array('class' => 'navigation') will return ' class="navigation"'
	 * 
	 * @param mixed $attributes
	 * @return
	 */
	public static function attributes($attributes = array()) 
	{
		if (is_array($attributes))
		{
			$output = '';
			foreach ($attributes as $key => $value)
			{
				$output .= " $key=" . '"' . check_plain($value) . '"';
			}
			
			return $output;
		}
	}
	
	/**
	 * Enter description here ...
	 * 
	 * @param unknown_type $breadcrumbs
	 * @return string
	 */
	public function breadcrumbs($breadcrumbs) 
	{
		$output = '';
		if (!empty($breadcrumbs))
		{
			$separator = '</li><li class="breadcrumb"> > </li><li class="breadcrumb">';
			
			$output = '<div class="breadcrumbs"><ul><li class="breadcrumb first">' . implode($separator, $breadcrumbs) . '</li><li class="breadcrumb"> > </li><li class="breadcrumb last">' . page_title() . '</li></ul></div>';
		}
		
		return $output;
	}
	
	/**
	 * Theme a heading (ie. <h1> element).
	 * 
	 * @param $text: the text to be included in the heading. Not translated here.
	 * @param $level: the level of the heading (1 - 6).
	 * @param $attributes: Optional attributes.
	 * @return themed heading.
	 */
	public function heading($text, $level = 2, $attributes = array()) 
	{
		if ($level > 6 || $level < 1)
		{
			Athena::setMessage('Invalid heading level requested ' . $level, 'error');
			$level = 6;
		}
		
		return "<h$level " . attributes($attributes) . '>' . $text . "</h$level>";
	}
	
	/**
	 * links()
	 * 
	 * Generally renders and links in a list
	 * 
	 * @param mixed $links
	 * @param mixed $attributes
	 * @return
	 */
	public function links($links, $attributes = array('class' => 'links')) 
	{
		$q = $_GET['q'];
		
		$output = '';
		$count = count($links);
		if ($count > 0)
		{
			$output = '<ul' . attributes($attributes) . '>' . "\n";
			
			$i = 0;
			foreach ($links as $key => $link)
			{
				$classes = array(
					'link-item'
				);
				
				$classes[] = ($i + 1) % 2 == 0 ? 'even' : 'odd';
				
				if ($i == 0)
				{
					$classes[] = 'first';
				}
				else if ($i == $count - 1)
				{
					$classes[] = 'last';
				}
				
				if (isset($link['#href']) && $link['#href'] == $q)
				{
					$class[] = 'active';
				}
				
				$output .= '<li class="' . implode(' ', $classes) . '">' . a($link) . '</li>' . "\n";
				$i++;
			}
			
			$output .= '</ul>' . "\n";
		}
		
		return $output;
	}
	
	/**
	 * menu()
	 * 
	 * Renders those menu links
	 * 
	 * @param mixed $menu
	 * @return
	 */
	public function menu($menu) 
	{
		return $this->links($menu, array('class' => 'ui-navigation menu'));
	}
	
	/**
	 * systemMessages()
	 * 
	 * Enter description here ...
	 * 
	 * @param unknown_type $messages
	 * @return string
	 */
	public function systemMessages($messages) 
	{
		$output = '';
		foreach ($messages as $type => $message_list)
		{
			if (!empty($message_list))
			{
				$output .= '<div class="' . $type . '"><ul>' . "\n";
				
				for($i = 0; $i < count($message_list); $i++)
				{
					$output .= '<li class="message">' . $messages[$type][$i] . '</li>' . "\n";
				}
				
				$output .= '</ul></div>' . "\n";
			}
		}
		
		return $output;
	}
	
	/**
	 * table()
	 * 
	 * Enter description here ...
	 * 
	 * @param array $table
	 * @return string
	 */
	public function table($table) 
	{
		$output = '<table' . (empty($table['#attributes']) ? '' : attributes($table['#attributes'])) . '>';
	
		if (isset($table['#header']) && count($table['#header']) > 0)
		{
			$output .= '<thead><tr>';
			
			foreach ($table['#header'] as $cell)
			{
				$output .= $this->tableCell($cell, NULL, TRUE);
			}
			
			$output .= '</tr></thead>';
		}
		
		if (isset($table['#rows']) && count($table['#rows']) > 0)
		{
			$output .= '<tbody>';
			
			$row_count = count($table['#rows']);
			foreach ($table['#rows'] as $index => $row)
			{
				$classes = array((($index + 1) % 2 == 0 ? 'even' : 'odd'));
				
				if ($index == 0)
				{
					$classes[] = 'first';
				}
				else if ($index == $row_count - 1)
				{
					$classes[] = 'last';
				}
				
				$output .= '<tr class="' . implode(' ', $classes) . '"' . (empty($row['#attributes']) ? '' : attributes($row['#attributes'])) . '>';
				
				if (isset($row['#cells']))
				{
					foreach ($row['#cells'] as $key => $cell)
					{
						$output .= $this->tableCell($cell, $key);
					}
				}
				
				$output .= '</tr>';
			}
			
			$output .= '</tbody>';
		}
		
		$output .= '</table>' . "\n";
		
		return $output;
	}
	
	/**
	 * tableCell()
	 * 
	 * Enter description here ...
	 * 
	 * @param array|string $cell
	 * @param string $key
	 * @param boolean $header
	 * @return string
	 */
	public function tableCell($cell, $key = NULL, $header = FALSE) 
	{
		$tag = 'td';
		if ($header !== FALSE)
		{
			$tag = 'th';
		}
		
		if (is_array($cell))
		{
			if (!empty($key))
			{
				if (empty($cell['#attributes']))
				{
					$cell['#attributes'] = array('class' => $key);
				}
				else
				{
					if (empty($cell['#attributes']['class']))
					{
						$cell['#attributes']['class'] = $key;
					}
					else
					{
						$cell['#attributes']['class'] .= ' ' . $key;
					}
				}
			}
			
			return '<' . $tag . (empty($cell['#attributes']) ? '' : attributes($cell['#attributes'])) . '>' . $cell['#value'] . '</' . $tag . '>' . "\n";
		}
		else
		{
			return '<' . $tag . (empty($key) ? '' : ' class="' . html_class($key) . '"') . '>' . $cell . '</' . $tag . '>' . "\n";
		}
	}
}

function attributes($attributes = array()) 
{
	return Theme::attributes($attributes);
}
